package com.dk.shared.events.common;

import com.dk.shared.utils.UserAuditorHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.task.TaskExecutor;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;

@Slf4j
public abstract class AbstractEventHandler<E extends ApplicationEvent> implements ApplicationListener<E> {
    private final Boolean asyncProcess;

    private final Boolean transaction;

    @Autowired(required = false)
    private TransactionTemplate transactionTemplate;

    @Autowired
    private TaskExecutor taskExecutor;

    protected AbstractEventHandler() {
        asyncProcess = false;
        transaction = true;
    }

    protected AbstractEventHandler(Boolean asyncProcess) {
        this.asyncProcess = asyncProcess;
        transaction = true;
    }

    protected AbstractEventHandler(Boolean asyncProcess, Boolean transaction) {
        this.asyncProcess = asyncProcess;
        this.transaction = transaction;
    }

    protected boolean shouldAsyncProcess(E event) {
        if (asyncProcess != null) {
            return asyncProcess;
        }
        return event instanceof AbstractLocalEvent;
    }

    @Override
    public final void onApplicationEvent(E event) {
        String usingUserToken = null;
        if (event instanceof AbstractBusEvent busEvent) {
            usingUserToken = busEvent.getToken();
        }
        if (event instanceof AbstractLocalEvent localEvent) {
            usingUserToken = localEvent.getToken();
        }
        log.debug("Using user token {} to handle event", usingUserToken);
        if (shouldAsyncProcess(event)) {
            log.debug("Using user token {} to handle event", usingUserToken);
            taskExecutor.execute(attachSecurityAndRequestContext(usingUserToken,
                    () -> processWithTransaction(event)));
        } else {
            attachSecurityAndRequestContext(usingUserToken,
                    () -> processWithTransaction(event)).run();
        }
    }

    private void processWithTransaction(E event) {
        log.debug("Receive an event {}", event);
        if (transactionTemplate != null && transaction) {
            transactionTemplate.executeWithoutResult(status -> handleEvent(event));
        } else {
            log.warn("handle event {} without transaction", event);
            handleEvent(event);
        }
    }

    protected Runnable attachSecurityAndRequestContext(String usingUserToken, Runnable runnable) {
        return () -> {
            log.debug("事件设置的 token：{}", usingUserToken);
            UserAuditorHelper.setCurrentToken(usingUserToken);
            runnable.run();
            UserAuditorHelper.removeCurrentToken();
        };
    }

    protected HttpServletRequest getCurrentRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            log.warn("Current request attributes is empty");
            return null;
        }
        if (requestAttributes instanceof ServletRequestAttributes) {
            return ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        log.warn("Not support request attributes type {}", requestAttributes.getClass());
        return null;
    }

    protected abstract void handleEvent(E event);

}
